《Effective Java》第七章:方法
本章主要讨论方法设计的几个方面:
- 如何处理参数和返回值
- 如何设计方法签名
- 如何为方法编写文档
第38条:检查参数的有效性
这条就是教我们对于公有方法,最好在方法体上的开头处加上参数检查,并且在文档中清楚地指出这些限制,比如1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 求模
* @param m
* @return
* @throws ArithmeticException 如何m小于等于0时抛出异常
*/
public BigInteger mod(BigInteger m) throws ArithmeticException
{
if(m.signum()<=0)
throw new ArithmeticException("Moduls <= 0:"+m);
//do something
return ...
}
假如是对于私有方法,通常使用断言来检查他们的参数1
2
3
4
5
6private void sort(long a[],int offset,int length)
{
assert a!=null:"排序数组不能为空";
assert offset >= 0&& offset<=a.length;
assert length>=0 && length<=a.length-offset;
}
assert
默认是关闭的,需要使用-enableassertions
或者-ea
的Java
参数来显示开启。
其实并不是对任何参数都加限制是件好事,因为参数的有效性检查会带来开销,我们应该在设计方法时,应该使他们尽可能的通用,并符合实际的需要。
第39条:必要时进行保护性拷贝
假设类的客户端会尽其所能的破坏这个类的约束条件,因此你必须保护性的设计程序。
下面希望设计的是一个不可能修改对象内部状态的类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public final class Perion{
private final Date start;
private final Date end;
public Period(Date start,Date end) throws IllegalArgumentException
{
if(start.compareTo(end)>0)
throw new IllegalArgumentException("开始时间大于结束时间");
this.start=start;
this.end=end;
}
public Date start()
{
return this.start;
}
public Date end()
{
return this.end;
}
}
上面的类Perion
似乎是不可变的,并且还加强了约束条件,
但是Date
这个类是引用类型,如果是这么使用这个类:1
2
3
4Date start=new Date();
Date end=new Date();
Perion p=new Perion(start,end);
start.setYear(78);//在这里进行了外部对Perion类内部对象的修改
你可以发现实例化之后的Date
会被修改掉,所以你需要保护性拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public final class Perion{
private final Date start;
private final Date end;
public Period(Date start,Date end) throws IllegalArgumentException
{
//一定要先赋值 在进行类型检查
this.start=new Date(start.getTime());
this.end=new Date(end.getTime());
if(this.start.compareTo(this.end)>0)
throw new IllegalArgumentException("开始时间大于结束时间");
}
public Date start()
{
//防止他们使用get方法来获取引用对象
return new Date(this.start.getTime());
}
public Date end()
{
return new Date(this.end.getTime());
}
}
但是可以发现这种方法虽然是安全了,但是代价较大,另一种更好地方法是不要用Date
引用对象存储再数据库,而是使用时间戳更加合适1
2private final long start;//其他就不多说了
private final long end;
简而言之,如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性的拷贝这些组件。
第40条:谨慎设计方法签名
设计方法的时候有下面三个设计原则:
- 谨慎地选择方法名称
方法的名称应该始终遵循标准的命名习惯
- 不要过于追求便利的方法
每个方法应该尽其所能,方法定义也不要太多,最好文档化,有测试和维护,还有接口优于类
- 避免过长的参数列表
- 把方法分解成多个方法,每个方法只需要这些参数的一个子集
- 创建辅助类,其实就是实体类,相信大家都懂的
- 采用Builder模式
第41条:慎用重载
对于重载方法的选择是静态的,而对于被覆盖方法的选择是动态的。
使用重载方法可能会让你在调用的时候产生混淆,普通用户根本不知道“对于一组给定的参数,其中哪个重载方法会被调用”。
例如:ArrayList
有一个构造器带一个int
参数,另一个构造器带Collection
参数,难以想象的情况下会不清楚调用哪个参数。
在Java1.5之前,所有的基本类型都是根本不同于所有的引用类型,但是当自动装箱出现之后,就不再如此了,他会带来真正的麻烦。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public static void main(String[] args)
{
Set<Integer> set=new TreeSet<Integer>();
List<Integer> list=new ArrayList<Integer>();
for(int i=-3;i<3;i++)
{
set.add(i);
list.add(i);
}
for(int i=0;i<3;i++)
{
set.remove(i);
list.remove(i);
}
System.out.println(set+" "+list);
}
当初写这段代码的本意可能使希望添加[-3~3]之后再移除[0~2],但是看结果你会发现
[-3, -2, -1] [-3, -2, -1]
关于上面的输出:
set.remove(i)
调用的是重写方法remove(E)
,这里的E
集合是Integer
的元素类型,将i
自动装箱到Integer
中- 但是
list.remove(i)
重载的却是remove(int index)
,它是按索引去移除元素,也因此得到了意料之外的结果。
简而言之:“能够重载的方法”并不意味着就”应该重载方法”,一般情况下,对于读个具有相同参数目的的方法来说,应该重载方法。
我想,其实重载还是很好使的,但是在重载的时候一般要避免可拆装箱类型或者有继承类型的出现,比如上例中的
remove(Object o)
以及remove(int index)
就是非常会让人混淆,你可以写成removeByIndex(int index)
^_^
第42条:慎用可变参数
在Java中,可以使用下面的语法来传递可变参数:1
2
3
4
5
6
7
8
9public int sum(int ... args)
{
int sum=0;
for(int i:args)
{
sum+=i;
}
return sum;
}
但是该方法可以不传参数调用,但是有时候需要编译1个或者多个参数的传入,而不是0到或者多个,得加上args.length
的检查,但是更好地方法是1
2
3
4
5
6
7
8
9public int sum(int firstArgs,int ... args)
{
int sum=firstArgs;
for(int i:args)
{
sum+=i;
}
return sum;
}
在重视参数性能的情况下,使用可变参数机制要特别小心,可变参数的方法每次调用都会进行一次数组的分配和初始化,所以建议先重载5个参数一下的方法(上一条还建议慎用重载呢-_-):1
2
3
4public int sum(int arg1)
public int sum(int arg1,int arg2)
public int sum(int arg1,int arg2,int arg3)
public int sum(int arg1,int arg2,int arg3,int arg4)
简而言之,在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是它们不应该被过度滥用
第43条:返回长度的数组或者集合,而不是null
当函数的返回类型集合类但是却没有集合元素返回时不要返回null
,因为开发人员可能会使用返回值来做下一个操作,所以最好你应该返回一个空得集合。1
2
3
4
5
6
7private static final List<Integer> EMPTY_DATA=new ArrayList<Integer>();
public static List<Integer> main(String[] args)
{
if(...)
return EMPTY_DATA;//而不是null
}
对,这个注意好,妈妈再也不用担心我这类方法出来之后有没有检查
null
了
第44条:为所有导出的API元素编写文档注释
这还用说嘛,最讨厌使用那么没有提供注释的API
了,你叫臣妾怎么顺心的用你的API
啊。
一般较为友好的注释是这么写的1
2
3
4
5
6
7/**
* 这里写你这个方法是干嘛的,有什么特征,或者使用样例、
* @param index 这里写参数的接口
* @return 返回值,一般是一个短语
* @exception 这里就是写方法可能抛出的异常以及原因
*/
E get(int int)
这里的注释支持简单的html
标签:
- 比如你想换行或者分段可以使用<p>d</p>
- 你想斜体就用<i>d</i>
- 你想保持注释中文本的格式就是用<pre>
还有:
- 使用<pre>{@code,…}</pre>来在注释中添加代码
- 使用<pre>{@link,…}</pre>跳转到指定类额注释
- 还有几个其他的,自己看
Eclipse
中的提示
简而言之,要为API
编写文档,注释是最好的,最有效的途径